home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Disc to the Future 2
/
Disc to the Future Part II Programmer's Reference (Wayzata Technology)(6013)(1992).bin
/
MAC
/
THINKC
/
4_0
/
SOUND_EX.AMP
< prev
next >
Wrap
Text File
|
1991-08-16
|
31KB
|
942 lines
In the interest of helping more people to grok the
beast that is the Sound Manager, I'm posting this
sample C code (I use THINK C 4.0). It's translated
from the Pascal code in Apple's SoundApp example
package, and seems to work well. With this code, you
can do three things:
1. Play up to three channels of async sound. It's
trivial to add the fourth; I just didn't need it.
2. Continuously play an async sound by waiting for
the callBack routine to alert you that the previous
sound is done and starting a new one.
3. Play a "song" consisting of different sound samples
by using the sound queue. The code I include is
hard-coded for my needs and will need to be modified
for your particular sound needs. This code plays
6 different samples in a specific ordering, then
loops through the last two samples forever.
Disclaimer: the Apple code is good. What I've modified
probably is not. This isn't meant to teach you how to
program well. It took me a hell of a long time to learn
how to do this stuff, simply because everyone is so
tight-lipped about using the Sound Manager. If I had
had this sample code when I started this program, life
would have been much simpler, and I hope that this makes
life simpler for someone else out there.
I've been running this code under System 7, but I think
that this stuff will work under some versions of system
6.0.x too, esp. 6.0.7.
Toby Smith
bluecow@unix.cis.pitt.edu
Notes about the code:
gSoundCool is a global variable I use to determine whether we should
bother trying to do multi-channel sound. Use the SysEnvRec to
determine what machine and system you're running, and set gSoundCool
accordingly.
Before using any of these routines, be sure to call InitSoundUnit()
first.
SingIt(a bunch of Handles to SND resources...) is my routine to play
a song consisting of sampled sounds. It queues up a bunch of sounds
to be played, as well as a callBack command when the queue is
getting low. When I receive the callBack flag, I send more
commands to the queue to keep it full (so the song plays
forever). For example,
SingIt(...)
if (gCalledBack) {
gCalledBack = FALSE;
LoadSndQueue2();
}
PlayIt(Handle) is the routine to play an async sound. When the
sound completes, gCalledBack will be set to TRUE. If you check
gCalledBack often enough, you can issue another PlayIt() command
so that it sounds like a continuous stream of sound. However,
this requires a lot of checking to avoid gaps. It's much easier
to use SingIt(). With SingIt(), you can do a lot of processing
and just check gCalledBack occasionally, sending another bunch
of commands as needed. If you're using really short samples,
embed the callBack command earlier on in your command queue,
to give you more time to realize that you need to send more
commands.
If you don't want to do a continuous stream of commands, you
should still check gCalledBack, since you should kill the
sound channel (using KillSound()) as soon as you don't need it
anymore.
PlayIt2() & PlayIt3() do the same thing as PlayIt(), but uses
different sound channels. By playing different sounds using
these different commands, you can have multi-channel sound
going. Note: PlayIt2() and PlayIt3() also set the the same
global variable, gCalledBack, when they complete their sounds.
This means that gCalledBack becomes completely worthless during
multi-channel playback. An easy solution is to create a
different callBack routine for each of the three routines, and
have a different global variable for each. I didn't need this
ability, so it's not in this code, but it's a trivial change.
That's about it. Hope this is helpful.
#include <SoundMgr.h>
#include <limits.h>
extern Boolean soundIsOn, gSoundCool;
/*These are used as flags in the sound channel to determine the state
of that channel. The 'snth' IDs are used when the channel is in use
to determine what that channel in intended for.*/
#define kNoSynth 0 /*no synth is specified*/
#define kChanComplete -1 /*channel has completed*/
#define kChanFree LONG_MAX /*channel is not in use*/
#define kSoundComplete 0x1234 /*flag for callBackCmd*/
#define kInitNone 0 /*no init options*/
#define kWait FALSE /*wait for the channel*/
#define kSMAsynch TRUE /*asynchronous Sound Manager call*/
#define NIL 0L
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*I have declared a few TYPEs below to help examine 'snd ' resources.
I have to break them up into individual pieces because they are
variable sized records.*/
typedef struct Snd1Header
{
int format;
int numSynths;
} Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;
typedef struct Snd2Header
{
int format;
int refCount;
} Snd2Header;
typedef struct SynthInfo
{
int synthID;
long initOption;
} SynthInfo, *SynthInfoPtr;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*I have created my own sound channel type. It extends the normal
sound channel by adding a few fields. To confirm that the channel
in question is mine, I set the userInfo field to something that I
can recognize. I keep the 'snd ' resource handle associated to this
channel too. This allows me to dispose of the data once the channel
has completed its duties.*/
typedef struct MyChanType /*this is a 1064 byte structure*/
{
SndChannel theChan; /*must keep sound channel as first field*/
Handle dataHandle;
} MyChanType, *MyChanPtr;
/*I╒ve had to re-define the SoundHeader to correctly match the current version.
This is correctly defined in MPW 3.2 or later.*/
typedef struct MySndHeader {
Ptr samplePtr;
long length;
Fixed sampleRate;
long loopStart;
long loopEnd;
short encode;
short baseNote;
char sampleArea[];
} MySndHeader, *MySndHeaderPtr;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*These four global pointers are to my sound channels. One of them has to
be global and I could utilize the fact that they are a linked list. That
would complicate things to some degree, and it really wouldn╒t have that
much of an advantage.*/
extern MyChanPtr gChan1;
extern MyChanPtr gChan2;
extern MyChanPtr gChan3;
extern MyChanPtr gChan4;
/*This is the global flag that is set once the sound channel╒s call back
procedure has been called. This, in my case, means the channel has
completed its duties and is time for disposing.*/
extern Boolean gCalledBack;
/*gChanOpen is a flag set to determine if the application has a sound
channel open. It╒s really not feasible to determine if a sound is being
made at any given point.*/
extern Boolean gChanOpen;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
Boolean InitMyChan()
{
gChan1 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
if (gChan1 != NIL) {
(*gChan1).theChan.qLength = stdQLength;
(*gChan1).theChan.userInfo = kChanFree;
} else
return(FALSE);
gChan2 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
if (gChan2 != NIL) {
(*gChan2).theChan.qLength = stdQLength;
(*gChan2).theChan.userInfo = kChanFree;
} else
return(FALSE);
gChan3 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
if (gChan3 != NIL) {
(*gChan3).theChan.qLength = stdQLength;
(*gChan3).theChan.userInfo = kChanFree;
} else
return(FALSE);
gChan4 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
if (gChan4 != NIL) {
(*gChan4).theChan.qLength = stdQLength;
(*gChan4).theChan.userInfo = kChanFree;
} else
return(FALSE);
return(TRUE);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr InitSoundUnit()
/*Create storage for each of the four channels (4 * 1064 bytes) and
initialize them. If any of the channels cannot be allocated, return an
error. Also initialized our global flag ╥gCalledBack.╙ An interesting
modification would be to allow the caller to pass in the number of
channels the application really intends on using. If the user only wants
one channel, then we could just allocate one instead of four. These
channels are used at interrupt time.*/
{
gChanOpen = FALSE;
gCalledBack = FALSE;
if (InitMyChan() == TRUE)
return(noErr);
else
return(MemError() );
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr HoldSnd(Handle sndHandle)
/*This is used to put the given sound resource into memory and hold it there.
I also use a MoveHHi to keep the heap from being fragmented. If this
fails, then I return an error. I dereference the handle and check if the
master pointer is NIL. This would mean the data could not be loaded.*/
{
if (sndHandle != NIL) {
LoadResource(sndHandle);
if ((*sndHandle) == NIL)
return(nilHandleErr); /*master pointer is NIL*/
else {
MoveHHi(sndHandle);
HLock(sndHandle);
return(noErr);
}
} else
return(nilHandleErr); /*user passed a NIL handle*/
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr SendQuiet( SndChannelPtr chan, Boolean immediate )
/*Given a channel, this will place a quietCmd into the channel╒s queue. I
use SndDoCommand with the noWait flag set to wait for the channel to
accept the command in the case it is currently full.
BUG NOTE: A sequence of notes and rests will not work unless quietCmds
are between them. Rests have to be made quiet before they rest, if that
makes any more sense. A noteCmd will loop, causing the sound in progress
to continue, until a quietCmd is received. */
{
SndCommand theCmd;
theCmd.cmd = quietCmd;
theCmd.param1 = 0;
theCmd.param2 = 0;
if (immediate)
return(SndDoImmediate(chan, &theCmd));
else
return(SndDoCommand(chan, &theCmd, kWait));
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void FreeChan( MyChanPtr myChan )
/*Test if the channel is free, and if not then call SndDisposeChannel.
This will release the synthesizer (snth resource) code and the
required hardware. If we didn╒t do this, no other channels would
work. I also test myChan for having a snd resource attached to the
channel. If so, then I mark it as purgeable and reset the data to NIL.
BUG NOTE: Calling SndDisposeChannel while or immediately after playing
a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
Issuing a quietCmd first kept the Sound Manager happy and my Mac from
crashing. */
{
OSErr theErr;
if ((*myChan).theChan.userInfo != kChanFree) {
theErr = SendQuiet((SndChannelPtr)(myChan), !kWait); /*ignore error*/
theErr = SndDisposeChannel((SndChannelPtr)(myChan), !kWait);
(*myChan).theChan.userInfo = kChanFree;
}
if ((*myChan).dataHandle != NIL) {
HUnlock((*myChan).dataHandle);
HPurge((*myChan).dataHandle);
(*myChan).dataHandle = NIL;
}
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
Boolean SoundCompletion()
/*This routine can be called to determine if the sound has completed. When
this is true, the sound data can be disposed of. The global ╥gCalledBack╙
determines whether the sound has completed or not and it is set by the
sound channel╒s completion routine. Soundcompletion is placed in the Main
segment because it is called by the event loop.*/
{
return(gCalledBack);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void DoSoundComplete()
/*This routine is called by an application that established a sound to be
played asynchronously. This is, in effect, the routine to be used after
the completion routine has been called. The application should call this
once SoundCompletion returns TRUE. In the case the application is using
multiple channels, we will only free a channel once it has been marked as
kChanComplete. I do not reset the gCalledBack until all the open channels
are freed.*/
{
if ((*gChan1).theChan.userInfo == kChanComplete)
FreeChan(gChan1);
if ((*gChan2).theChan.userInfo == kChanComplete)
FreeChan(gChan2);
if ((*gChan3).theChan.userInfo == kChanComplete)
FreeChan(gChan3);
if ((*gChan4).theChan.userInfo == kChanComplete)
FreeChan(gChan4);
if (((*gChan1).theChan.userInfo == kChanFree)
&& ((*gChan2).theChan.userInfo == kChanFree)
&& ((*gChan3).theChan.userInfo == kChanFree)
&& ((*gChan4).theChan.userInfo == kChanFree)) {
gCalledBack = FALSE;
gChanOpen = FALSE; /*no longer making noises*/
}
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void FreeAllChans()
/*This is the routine that will force all channels to be released. It also
resets the gCalledBack flag. This is used by all routines just before
opening a new channel to force all channels to be disposed. */
{
FreeChan(gChan1);
FreeChan(gChan2);
FreeChan(gChan3);
FreeChan(gChan4);
gCalledBack = FALSE;
gChanOpen = FALSE; /*no longer making noises*/
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void KillSound()
/*This is used to free up all the sound data and channels in use. There are
two reasons for using this routine. One is after a sound has completed
the other is to terminate a sound that may be in progress. For this
second reason, this routine should always be used before playing a new
sound. */
{
if (SoundCompletion() ) /*why were we called?*/
DoSoundComplete(); /*from completion*/
else
FreeAllChans(); /*or just because*/
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr SoundComplete( SndChannelPtr chan )
/*I use this to install the callBackCmd into the given channel. I need to
pass in our A5 along with the command so that the callBack routine can
access my globals. I also wait until the channel is ready for another
command in the case of the channel being full. Once the Sound Manager
calls my call back procedure I will dispose of the channel. So, this is
the last sound command to be sent to a channel. I pass to the call back
A5 in the second parameter of the callBackCmd. Refer to Tech Note #208.*/
{
SndCommand theCmd;
theCmd.cmd = callBackCmd;
theCmd.param1 = kSoundComplete;
theCmd.param2 = SetCurrentA5();
return(SndDoCommand(chan, &theCmd, kWait));
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr SndDataAvailable(Handle sndHandle)
/*Given a resource handle, this will attempt to load it into memory. If the
data is not available, then return an error.*/
{
if (sndHandle != NIL) {
LoadResource(sndHandle);
if (*sndHandle == NIL)
return(nilHandleErr); /*master pointer is NIL*/
} else
return(nilHandleErr); /*user passed a NIL handle*/
return(noErr);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
SynthInfo GetSynthInfo(Handle sndHandle)
/*This routine will return the 'snth' resource ID specified by the sound.
I use this to determine if the given sound will work with _SndPlay.
This routine does not require the data to be in memory when called. It
also doesn╒t lock it down while looking for the information. I will
mark the resource as being purgeable in the case the resource attributes
has it╒s non-purgeable bit set.*/
{
Ptr soundPtr;
OSErr theErr;
SynthInfo ourSynth;
ourSynth.synthID = kNoSynth;
ourSynth.initOption = 0;
theErr = SndDataAvailable(sndHandle);
if (theErr == noErr) {
soundPtr = *sndHandle;
if ((*(Snd1HdrPtr)(soundPtr)).format == firstSoundFormat) {
if ((*(Snd1HdrPtr)(soundPtr)).numSynths != kNoSynth) {
soundPtr = (Ptr)(soundPtr + sizeof(Snd1Header));
ourSynth.synthID = (*(SynthInfoPtr)(soundPtr)).synthID;
ourSynth.initOption = (*(SynthInfoPtr)(soundPtr)).initOption;
}
} else { /*snd is a format 2 for HyperCard*/
ourSynth.synthID = sampledSynth;
ourSynth.initOption = 0; /*no options currently supported*/
}
HPurge(sndHandle);
}
return(ourSynth);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd )
/*This will be called at interrupt time by the Sound Manager when it
receives a callBackCmd. I use the second parameter of the command to hold
my application╒s A5 reference. I first set up A5 so that I can access my
globals. I mark the given channel as being complete and set gCalledBack
to TRUE. This lets the application know that the callBackCmd has been
processed. The callBackCmd can be used for other purposes, and the first
parameter of the command could be a flag to a more extensive routine.
Synchronizing the application with the channel is possible with this
method.
WARNING: This routine MUST be resident in memory and cannot make a call
to a non-resident segment. I put this into the Main segment because of
this.
BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
'snd '. A bogus callBackCmd is placed into the queue immediately after
the bufferCmd used to play the sound. This bogus callBackCmd will cause
my callBackProc to be called when I wasn╒t expecting it. I have been
using the command╒s second parameter to contain my A5 address. If I╒m
given a bogus callBackCmd, it would be really bad to set A5 address to
this bogus parameter in the command. I found that the bogus callBackCmd
contains the handle to the 'snd ' passed in to _SndPlay. I also found
that param1 contains the handle╒s state bits (results of HGetState). To
work with this bug I set my real callBackCmd╒s param1 to a specific value
when I installed it into the queue. See the SoundComplete routine. Then
I test the callBackCmd to make sure I╒m dealing with the real one.*/
{
long theA5;
if ((*theCmd).param1 == kSoundComplete) { /*if it╒s my callBackCmd*/
theA5 = SetA5((*theCmd).param2); /*refer to tech note 208*/
(*chan).userInfo = kChanComplete; /*this channel is done*/
gCalledBack = TRUE;
theA5 = SetA5(theA5); /*restore original A5*/
}
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
Boolean IsMyChan(SndChannelPtr chan)
{
return(((chan == (SndChannelPtr)(gChan1)) /*is it one of ours?*/
|| (chan == (SndChannelPtr)(gChan2))
|| (chan == (SndChannelPtr)(gChan3))
|| (chan == (SndChannelPtr)(gChan4))));
}
Boolean CompatibleChan(SndChannelPtr waveChan)
{
return(((*waveChan).userInfo == waveTableSynth) /*wave or..*/
|| ((*waveChan).userInfo == kChanFree)); /*free chan*/
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr ChanAvailable(SndChannelPtr theChan)
/*This routine will test the given channel to see if it will really produce
sound. The Sound Manager in System 6.0x will return noErr even if the
channel isn╒t going to work. In the future the Sound Manager will return
the proper error. Until then, I use this routine to determine this for me.
There can only be a single channel at any time, unless I have the wave
table synthesizer open. This will allow four channels. Channels have
a pointer to the next channel, and if this is not NIL I suspect the
given channel will not work. I test the given channel for being a
wave type, and if so I need to see if the other channels I╒ve got are
also wave type. If it doesn╒t look like the channels is available, I
return badChannel. It is important to set the userInfo field of a channel
before calling this routine!
BUG NOTE: If an application is not using the Sound Manager and instead
uses the older Sound Driver, any given channel will fail. Or if the other
application does not release is channels, then my channels will not work.
The most noticeable offender of this is HyperCard. Friendly applications
will dispose of their channels at suspend/resume times or ASAP.*/
{
if ((*theChan).nextChan != NIL) { /*looks bad*/
if ((*theChan).userInfo == waveTableSynth) { /*last attempt*/
if (IsMyChan((*theChan).nextChan)
&& CompatibleChan((SndChannelPtr)(gChan1))
&& CompatibleChan((SndChannelPtr)(gChan2))
&& CompatibleChan((SndChannelPtr)(gChan3))
&& CompatibleChan((SndChannelPtr)(gChan4)))
return(noErr); /*got lucky*/
else
return(badChannel);
}
}
return(noErr);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
OSErr GetNoSynthChan()
/*This is the routine to create a channel that isn╒t associated with any
synthesizer. Why? Because if you wanted to use _SndPlay asynchronously
you need to get such a channel. When I create a channel I will set the
userInfo field marking it with the 'snth' associated to it. This must be
done before calling ChanAvailable.
BUG NOTE: Do not use a channel already associated to a snth with
_SndPlay. This causes the Sound Manager to install a second copy of the
same snth.
I didn't understand why it had any parameters for my purposes so I removed
them since they were being a pain in my butt anyway. */
{
OSErr theErr;
FreeChan(gChan1);
theErr = SndNewChannel(&gChan1, kNoSynth,
kInitNone, DoCallBack);
if (theErr == noErr) {
(*gChan1).theChan.userInfo = kNoSynth;
theErr = ChanAvailable((SndChannelPtr)(gChan1));
}
if (theErr != noErr)
FreeChan(gChan1);
else
gChanOpen = TRUE; /*now we╒re making noise*/
return(theErr);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void PlayIt(Handle sndHandle)
/*Given a sound resource, this routine will call _SndPlay. The snd must
be either a format 2 or format 1 that contains snth information.
Using _SndPlay asynchronously requires us to lock the snd prior to
calling the trap. The reason being is _SndPlay remembers the state of the
lock bit using _HGetState and _HSetState. If the snd is unlocked when
it╒s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
This would be bad when using the sound asynchronously. If the sound being
passed in happens to be a compressed sound created with MACE, it will ╥do
the right thing.╙ If MACE isn╒t around the Sound Manager will pretend to
play a sound but nothing will be heard.
BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
a Memory Manager error when allocating its internal buffer. There is a
call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
randomly to memory. Also, the pointer is allocated into the application╒s
heap instead of the system╒s.
BUG NOTE: _SndPlay when using System 6.0.4 and a sampled sound will send
a bogus callBackCmd into the channel. This will cause the user╒s call
back procedure to be called as soon as the sound has completed. Refer
to the DoCallBack routine for details. */
{
OSErr theErr;
int synthID;
if (!soundIsOn)
return;
theErr = HoldSnd(sndHandle); /* hold on to the sound */
if (theErr == noErr) {
theErr = GetNoSynthChan();
(*gChan1).dataHandle = sndHandle; /* so FreeAllChans can dispose of data */
if (theErr == noErr) {
synthID = GetSynthInfo(sndHandle).synthID;
if (synthID != kNoSynth) {
(*gChan1).theChan.userInfo = synthID;
theErr = SndPlay((SndChannelPtr)(gChan1), sndHandle, kSMAsynch);
if (theErr == noErr)
theErr = SoundComplete((SndChannelPtr)(gChan1));
} else
theErr = badFormat;
}
}
if (theErr != noErr)
FreeChan(gChan1);
}
OSErr Get2NoSynthChan()
{
OSErr theErr;
FreeChan(gChan2);
theErr = SndNewChannel(&gChan2, kNoSynth,
kInitNone, DoCallBack);
if (theErr == noErr) {
(*gChan2).theChan.userInfo = kNoSynth;
theErr = ChanAvailable((SndChannelPtr)(gChan2));
}
if (theErr != noErr)
FreeChan(gChan2);
else
gChanOpen = TRUE; /*now we╒re making noise*/
return(theErr);
}
void PlayIt2(Handle sndHandle)
{
OSErr theErr;
int synthID;
if (!soundIsOn)
return;
if (!gSoundCool) {
PlayIt(sndHandle);
return;
}
theErr = HoldSnd(sndHandle); /* hold on to the sound */
if (theErr == noErr) {
theErr = Get2NoSynthChan();
(*gChan2).dataHandle = sndHandle; /* so FreeAllChans can dispose of data */
if (theErr == noErr) {
synthID = GetSynthInfo(sndHandle).synthID;
if (synthID != kNoSynth) {
(*gChan2).theChan.userInfo = synthID;
theErr = SndPlay((SndChannelPtr)(gChan2), sndHandle, kSMAsynch);
if (theErr == noErr)
theErr = SoundComplete((SndChannelPtr)(gChan2));
} else
theErr = badFormat;
}
}
if (theErr != noErr)
FreeChan(gChan2);
}
OSErr Get3NoSynthChan()
{
OSErr theErr;
FreeChan(gChan3);
theErr = SndNewChannel(&gChan3, kNoSynth,
kInitNone, DoCallBack);
if (theErr == noErr) {
(*gChan3).theChan.userInfo = kNoSynth;
theErr = ChanAvailable((SndChannelPtr)(gChan3));
}
if (theErr != noErr)
FreeChan(gChan3);
else
gChanOpen = TRUE; /*now we╒re making noise*/
return(theErr);
}
void PlayIt3(Handle sndHandle)
{
OSErr theErr;
int synthID;
if (!soundIsOn)
return;
if (!gSoundCool) {
PlayIt(sndHandle);
return;
}
theErr = HoldSnd(sndHandle); /* hold on to the sound */
if (theErr == noErr) {
theErr = Get3NoSynthChan();
(*gChan3).dataHandle = sndHandle; /* so FreeAllChans can dispose of data */
if (theErr == noErr) {
synthID = GetSynthInfo(sndHandle).synthID;
if (synthID != kNoSynth) {
(*gChan3).theChan.userInfo = synthID;
theErr = SndPlay((SndChannelPtr)(gChan3), sndHandle, kSMAsynch);
if (theErr == noErr)
theErr = SoundComplete((SndChannelPtr)(gChan3));
} else
theErr = badFormat;
}
}
if (theErr != noErr)
FreeChan(gChan3);
}
long GetSndDataOffset(Handle );
void LoadSndQueue();
static SndCommand theCmd1, theCmd2, theCmd3, theCmd4, theCmd5, theCmd6;
void SingIt(Handle sndHandle1, Handle sndHandle2, Handle sndHandle3, Handle sndHandle4,
Handle sndHandle5, Handle sndHandle6)
{
OSErr theErr;
int synthID;
MySndHeaderPtr dataPtr;
long dataOffset;
theErr = HoldSnd(sndHandle1); /* hold on to the sound */
theErr = HoldSnd(sndHandle2); /* hold on to the sound */
theErr = HoldSnd(sndHandle3); /* hold on to the sound */
theErr = HoldSnd(sndHandle4); /* hold on to the sound */
theErr = HoldSnd(sndHandle5); /* hold on to the sound */
theErr = HoldSnd(sndHandle6); /* hold on to the sound */
if (theErr == noErr) {
theErr = SndNewChannel(&gChan1, sampledSynth, initMono, DoCallBack);
(*gChan1).dataHandle = sndHandle1; /* so FreeAllChans can dispose of data */
if (theErr == noErr) {
dataOffset = GetSndDataOffset(sndHandle1);
dataPtr = (MySndHeaderPtr)((long)(*sndHandle1) + dataOffset);
theCmd1.cmd = bufferCmd;
theCmd1.param1 = 0;
theCmd1.param2 = (long)(dataPtr);
}
if (theErr == noErr) {
dataOffset = GetSndDataOffset(sndHandle2);
dataPtr = (MySndHeaderPtr)((long)(*sndHandle2) + dataOffset);
theCmd2.cmd = bufferCmd;
theCmd2.param1 = 0;
theCmd2.param2 = (long)(dataPtr);
}
if (theErr == noErr) {
dataOffset = GetSndDataOffset(sndHandle3);
dataPtr = (MySndHeaderPtr)((long)(*sndHandle3) + dataOffset);
theCmd3.cmd = bufferCmd;
theCmd3.param1 = 0;
theCmd3.param2 = (long)(dataPtr);
}
if (theErr == noErr) {
dataOffset = GetSndDataOffset(sndHandle4);
dataPtr = (MySndHeaderPtr)((long)(*sndHandle4) + dataOffset);
theCmd4.cmd = bufferCmd;
theCmd4.param1 = 0;
theCmd4.param2 = (long)(dataPtr);
}
if (theErr == noErr) {
dataOffset = GetSndDataOffset(sndHandle5);
dataPtr = (MySndHeaderPtr)((long)(*sndHandle5) + dataOffset);
theCmd5.cmd = bufferCmd;
theCmd5.param1 = 0;
theCmd5.param2 = (long)(dataPtr);
}
if (theErr == noErr) {
dataOffset = GetSndDataOffset(sndHandle6);
dataPtr = (MySndHeaderPtr)((long)(*sndHandle6) + dataOffset);
theCmd6.cmd = bufferCmd;
theCmd6.param1 = 0;
theCmd6.param2 = (long)(dataPtr);
}
if (theErr == noErr)
LoadSndQueue();
}
if (theErr != noErr)
FreeChan(gChan1);
}
typedef int * IntPtr ;
typedef SndCommand * SndCmdPtr;
long GetSndDataOffset(Handle sndHandle)
{
int waveLength, dataType, synths, howManyCmds;
Ptr cruisePtr;
long myreturn;
myreturn = 0;
dataType = kNoSynth;
waveLength = 0;
cruisePtr = *sndHandle;
if (cruisePtr != 0) {
if ((*((Snd1HdrPtr)cruisePtr)).format == firstSoundFormat) {
synths = (*((Snd1HdrPtr)cruisePtr)).numSynths;
cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(Snd1Header));
cruisePtr = (Ptr)((long)(cruisePtr) + (sizeof(SynthInfo) * synths));
} else
cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(Snd2Header));
howManyCmds = *((IntPtr)(cruisePtr));
cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(howManyCmds));
do {
switch ((*((SndCmdPtr)(cruisePtr))).cmd) {
case (soundCmd + dataPointerFlag):
case (bufferCmd + dataPointerFlag):
dataType = sampledSynth;
myreturn = (*((SndCmdPtr)(cruisePtr))).param2;
howManyCmds = 0;
break;
case (waveTableCmd + dataPointerFlag):
dataType = waveTableSynth;
waveLength = (*((SndCmdPtr)(cruisePtr))).param1;
myreturn = (*((SndCmdPtr)(cruisePtr))).param2;
howManyCmds = 0;
break;
default:
cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(SndCommand));
howManyCmds = howManyCmds - 1;
break;
}
} while (howManyCmds >= 1);
}
return(myreturn);
}
void LoadSndQueue()
{
SndCommand myCmd;
myCmd.cmd = callBackCmd;
myCmd.param1 = kSoundComplete;
myCmd.param2 = SetCurrentA5();
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd4, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd5, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd5, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &myCmd, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd5, FALSE);
}
void LoadSndQueue2()
{
SndCommand myCmd;
myCmd.cmd = callBackCmd;
myCmd.param1 = kSoundComplete;
myCmd.param2 = SetCurrentA5();
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &myCmd, FALSE);
SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE);
}